#!/usr/bin/env python
#-*- coding: utf-8 -*-

# (BSD 2-clause)
# Copyright (c) 2011, Mario Hock, Michael Tänzer, Karin Schmidt.
# All rights reserved.
# 
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
# 
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 
# THIS SOFTWARE IS PROVIDED BY THE INSTITUTE OF TELEMATICS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE ARIBA PROJECT OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and documentation
# are those of the authors and should not be interpreted as representing
# official policies, either expressed or implied, of the copyright holders.

import pygtk
pygtk.require('2.0')

import gtk
import glib
import gobject
import sys
import urllib
import os
from .user_list import UserList
from .chat import Chat
from .user import User
import omnibus_steeringwheel as steeringwheel

# drag & drop from nautilus
TARGET_TYPE_URI_LIST = 80


class OmnibusDriver(object):
    remote_files_partner = ""
    drag_push_filename = ""
    stat_timer_id = 0
    
    def __init__(self, **connection_args):
        self._connection = steeringwheel.Connection(**connection_args)
        
        ## OK: connection established
        print "Omnibus-Backend up and running. Connection established."
        
        self.builder = gtk.Builder()
        self.builder.add_from_file(
                os.path.join(
                    os.path.dirname(__file__),"bus-driver.glade")
                )
        self.builder.connect_signals(self)
        
        # overwrite user setting to always display button icons
        # TODO: figure out whether this is bad
        settings = gtk.settings_get_default()
        settings.props.gtk_button_images = True
        
        # XXX drag & drop test
        # important: need to add "| gtk.gdk.ACTION_MOVE" for KDE
        dnd_list = [ ( 'text/uri-list', 0, TARGET_TYPE_URI_LIST ) ]
        self.builder.get_object('send_files_tree').drag_dest_set(gtk.DEST_DEFAULT_MOTION | gtk.DEST_DEFAULT_HIGHLIGHT | gtk.DEST_DEFAULT_DROP, dnd_list, gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
        
        others_shares_tree = self.builder.get_object('remote_files_tree')
        others_shares_tree.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
        
        
        self._connection.register_event_handler(self.filetransfer_handler,
                steeringwheel.EVENT_TYPE.TRANSFER_LIST)
        self._connection.register_event_handler(
                lambda connection, *_, **__: connection.stat(),
                steeringwheel.EVENT_TYPE.TRANSFER_UPDATE)
        self._connection.register_event_handler(
                lambda connection, *_, **__: connection.list_users(),
                steeringwheel.EVENT_TYPE.USER_UPDATE)
        
        def online_status_handler(_, __, online_status, **___):
            self.builder.get_object('overlay_status_online').set_visible(online_status)
            self.builder.get_object('overlay_status_offline').set_visible(not online_status)
        
        self._connection.register_event_handler(online_status_handler,
                steeringwheel.EVENT_TYPE.ONLINE_STATUS)
        
        Chat.register_event_handlers(self._connection)
        
        
        def info_handler(_, __, message, **___):
            print "INFO:\t" + message
        
        def warn_handler(_, __, message, **___):
            print "WARN:\t" + message

        ## XXX display omnibus debug messages
        def debug_handler(_, __, message, **___):
            print "DEBUG:\t" + message
        
        def error_handler(_, __, message, **___):
            print >> sys.stderr, "ERROR: ", message

        self._connection.register_event_handler(
                info_handler,
                steeringwheel.EVENT_TYPE.INFO)
        
        self._connection.register_event_handler(
                warn_handler,
                steeringwheel.EVENT_TYPE.WARN)
        
        self._connection.register_event_handler(
                error_handler,
                steeringwheel.EVENT_TYPE.ERROR)
        
        
        self.user_list = UserList(self.builder, self._connection)
        
        ## XXX tests mit timeouts und idle
        gobject.idle_add(self.idle_cb)
        #gobject.timeout_add(1000, self.timer_cb)
        
        glib.io_add_watch(self._connection.get_socket(),
                          glib.IO_IN,
                          self._connection.read_handler)
    
    
    def run(self):
        try:
            gtk.main()
        except KeyboardInterrupt:
            self.quit()
    
    def quit(self):
        self._connection.quit_backend()
        gtk.main_quit()


    def on_main_window_delete_event(self, *_):
        self.quit()
        return False
    
    
    def idle_cb(self):
      self._connection.list_users()
      return False
      
    def timer_cb(self):
#        print "timer."  ## XXX
        self._connection.stat()
        
        self.stat_timer_id = 0
        return False
    
    def open_chat(self, user_name):
        # TODO: probably remove to avoid indirection
        return Chat.open_chat(self._connection, user_name)
    
    
    ## ACTION-LISTENERS: ##  ##TODO irgendwo übersichtlich hin..
    def on_ok_clicked(self, action, *args):
        self.command_entered()
        inp = self.builder.get_object('input')
        self.builder.get_object('main_window').set_focus(inp)
    
    
    def on_input_activate(self, action, *args):
        self.command_entered()
    
    
    def on_look_dialog_delete_event(self, action, *args):
        self.builder.get_object('look_dialog').set_visible(False)
        return True
    def on_look_dialog_close_clicked(self, action, *args):
        self.builder.get_object('look_dialog').set_visible(False)
    
    def on_add_user_action_activate(self, *_):
        self.builder.get_object('meet_error').set_text('')
        entry = self.builder.get_object('meet_entry')
        entry.set_text('')
        dialog = self.builder.get_object('meet_dialog')
        dialog.set_focus(entry)
        dialog.show()
    
    def on_meet_dialog_delete_event(self, dialog, *_):
        dialog.hide()
        return True
    
    def on_meet_cancel_clicked(self, *_):
        self.builder.get_object('meet_dialog').hide()
    
    def on_meet_entry_activate(self, entry, *_):
        phrase = unicode(entry.get_text(), 'utf-8').strip()
        if phrase == '':
            self.builder.get_object('meet_error').set_text('The phrase must '+
                                    'not be empty')
            return
        
        self._connection.meet(phrase)
        self.builder.get_object('meet_dialog').hide()
    
    def on_meet_ok_clicked(self, *_):
        self.builder.get_object('meet_entry').activate()
    
    ## Treeview "Users", row-activated
    def on_users_tree_row_activated(self, treeview, row, column, *_):
        User.current_user.on_users_tree_row_activated()
    
    # user was selected
    def on_users_tree_cursor_changed(self, users_tree, *_):
        self.user_list.selection_updated()
            

    ## Treeview "Remote-Files", row-activated
    def on_remote_files_tree_row_activated(self, treeview, row_path, column):
        User.current_user.on_remote_files_tree_row_activated(row_path)
    
    def on_remote_files_tree_row_expanded(self, treeview, row, path, *_):
        User.current_user.on_remote_files_tree_row_expanded(row)
    
    def on_others_shares_download_activate(self, action, *_):
        User.current_user.on_others_shares_download_activate()
        
        
    ## paste button
    def on_send_paste_files_button_clicked(self, action, *_):
        if ( gtk.Clipboard().wait_is_uris_available() ):
            clip = gtk.Clipboard().wait_for_text()
            paths = clip.split("\n") # we may have more than one file
            for path in paths:
                if os.path.isfile(path) or os.path.isdir(path): # only real files and dirs
                    User.current_user._send_add_path(path)
        
        
    ## Drag & Drop, uri dropped
    def on_send_files_tree_drag_data_received(self, widget, context, x, y, selection, target_type, timestamp):
        if (target_type == TARGET_TYPE_URI_LIST ) :
            uri = selection.data.strip('\r\n\x00')
            uri_splitted = uri.split() # we may have more than one file dropped
            for uri in uri_splitted:
                path = self.get_file_path_from_dnd_dropped_uri(uri)
                if os.path.isfile(path) or os.path.isdir(path): # only real files and dirs
                    User.current_user._send_add_path(path)
    
    ## HELPER for Drag & Drop: extraxt filenames from uri
    def get_file_path_from_dnd_dropped_uri(self, uri):
        # get the path to file
        path = ""
        if uri.startswith('file:\\\\\\'): # windows
            path = uri[8:] # 8 is len('file:///')
        elif uri.startswith('file://'): # nautilus, rox
            path = uri[7:] # 7 is len('file://')
        elif uri.startswith('file:'): # xffm
            path = uri[5:] # 5 is len('file:')

        path = urllib.url2pathname(path) # escape special chars
        path = path.strip('\r\n\x00') # remove \r\n and NULL

        return path


    ## Drag & Drop, send
    def on_push_drag_send_clicked(self, action, *args):
        if ( self.drag_push_filename != "" ):
            self._connection.push(self.remote_files_partner,
                                  self.drag_push_filename)
            
            self.drag_push_listen(True)
            self.builder.get_object('look_dialog').set_visible(False)
    
    ## Drag & Drop, cancel
    def on_push_drag_cancel_clicked(self, action, *args):
        self.drag_push_listen(True)
    
    ## HELPER: remove dropped uri
    def drag_push_listen(self, can_drop):
        if ( can_drop ):
            self.drag_push_filename = ""
            
        self.builder.get_object('push_drag_area').set_visible(can_drop)
        self.builder.get_object("push_drag_ok_box").set_visible(not can_drop)

    
    ### Transfers Buttons
    ## HELPER for the transfers button
    def get_trans_selection(self):
        sel = self.builder.get_object("transfers_tree").get_selection()
        model, itr = sel.get_selected()
        if ( itr ):
            if ( model.get_value(itr, 0) == "gtk-sort-ascending" ):
                return model.get_value(itr, 1), True, model.get_value(itr, 7)
            elif ( model.get_value(itr, 0) == "gtk-sort-descending" ):
                return model.get_value(itr, 1), False, model.get_value(itr, 7)
            else:
                return model.get_value(itr, 1), False, model.get_value(itr, 7) ## FIXME incoming ist nicht korrekt, aber die buttons sollen eh nix machen, wenn finished..

    ## Transfers-Pause
    def on_transfers_pause_clicked(self, action, *args):
        try:
            key, incoming, state = self.get_trans_selection()
            if ( state == "gtk-media-play" ):
                if ( incoming ):
                    self._connection.pause_transfer(key)
                else:
                    self._connection.stop_transfer(key)
        except:
            pass
    
    ## Transfers-Play (resume)
    def on_transfers_play_clicked(self, action, *args):
        try:
            key, incoming, state = self.get_trans_selection()
            if ( incoming ):
                if ( state == "gtk-execute" ): ## FIXME, die stock codes sind nicht die zustände die hier verwendet werden sollen
                    self._connection.accept_transfer(key)
                elif ( state == "gtk-media-pause" ):
                    self._connection.resume_transfer(key)
            elif ( state == "gtk-media-pause" ):
                self._connection.continue_transfer(key)    
        except:
            pass

    ## Transfers-Cancel
    def on_transfers_cancel_clicked(self, action, *args):
        try:
            key, incoming, state = self.get_trans_selection()
            if ( state != "gtk-ok" ):
                if ( incoming ):
                    self._connection.decline_transfer(key)
                else:
                    self._connection.abort_transfer(key)
        except:
            pass


    ## Pause all transfers
    def on_transfers_pause_all_clicked(self, action, *args):
        self._connection.pause_all_transfers()
        
    ## open transferred
    def on_open_transferred_clicked(self, action, *args):
        self._connection.open_incoming()
    
    def on_share_selector_switch_page(self, notebook, page, page_num, *_):
        # param page not usable
        User.current_user.on_share_selector_switch_page(page_num)
    
    def on_send_user_not_connected_error_button_clicked(self, *_):
        self.builder.get_object('send_user_not_connected_error_expander').\
                set_expanded(False)
    
    def on_server_check_button_toggled(self, button, *_):
        User.current_user.on_server_check_button_toggled(button)
    
    def on_send_add_file_activate(self, action, *_):
        self.builder.get_object('send_file_chooser_dialog').show()
        self._send_file_chooser_user = User.current_user
    
    def on_send_file_chooser_cancel_activate(self, action, *_):
        self.builder.get_object('send_file_chooser_dialog').hide()
    
    def on_send_file_chooser_dialog_delete_event(self, dialog, *_):
        dialog.hide()
        return True
    
    def on_send_file_chooser_open_activate(self, action, *_):
        self._send_file_chooser_user.on_send_file_chooser_open_activate()
        self.builder.get_object('send_file_chooser_dialog').hide()
    
    def on_send_files_selected_toggled(self, cellrenderer, path, *_):
        User.current_user.on_send_files_selected_toggled(path)
    
    def on_send_send_activate(self, action, *_):
        User.current_user.on_send_send_activate()
    
    def on_my_shares_add_file_activate(self, action, *_):
        self.builder.get_object('my_shares_file_chooser_dialog').show()
        self._send_file_chooser_user = User.current_user
    
    def on_my_shares_file_chooser_cancel_activate(self, action, *_):
        self.builder.get_object('my_shares_file_chooser_dialog').hide()
    
    def on_my_shares_file_chooser_open_activate(self, action, *_):
        User.current_user.on_my_shares_file_chooser_open_activate()
        self.builder.get_object('my_shares_file_chooser_dialog').hide()
    
    def on_user_connect_action_activate(self, action, *_):
        User.current_user.on_user_connect_action_activate(action)
    
    def on_user_get_shares_list_action_activate(self, action, *_):
        User.current_user.on_user_get_shares_list_action_activate()
    
    def on_user_chat_action_activate(self, action, *_):
        user = self.user_list.get_current_user()
        if not user:
            return
        
        self.open_chat(user.get_name())
        
    def on_overlay_status_event_button_press_event(self, action, *_):
        self._connection.request_online_status()
    
    
    def filetransfer_handler(self, connection, _,
                             incoming, outgoing, finished, **__):
        ## XXX test, selection
        selection = self.builder.get_object("transfers_tree").get_selection()
        model, itr = selection.get_selected()
        path = ''
        if itr:
            path = model.get_path(itr)
        
        model.clear()
        active = self.filetransfer_add_all('gtk-sort-ascending',
                                           model,
                                           incoming)
        # FIXME: nicht direkt die stock-id eintragen !!
        active |= self.filetransfer_add_all('gtk-sort-descending',
                                            model,
                                            outgoing)
        self.filetransfer_add_all('gtk-ok', model, finished)
        if path:
            selection.select_path(path)
        
        # active transfers
        if active:
            # activate timer (if not already running)
            if self.stat_timer_id == 0 :
                self.stat_timer_id = gobject.timeout_add(1000, self.timer_cb)
        else:
            # stop timer, if still running
            if self.stat_timer_id != 0 :
                gobject.source_remove(self.stat_timer_id)
                self.stat_timer_id = 0
    
    
    ## HELPER: add a filetransfer into treeview_model
    def filetransfer_add_all(self, inout, model, transfers):
        active = False
        
        for transfer in transfers:
            assert isinstance(transfer, steeringwheel.Filetransfer)
            
            if ( transfer.size > 0 ):
                progress = transfer.transferred*100/transfer.size
            else:
                progress = 100
            state = self.state_to_stock_id(transfer.state)
            if ( state == "gtk-media-play" ): #XXX do not check on stock ids
                active = True

            model.append((inout, transfer.key, transfer.filename,
                          transfer.partner, transfer.transferred,
                          self.human_filesize(transfer.size),
                          progress, state))
        
        return active
        
    
    ## HELPER: filetransfers
    def state_to_stock_id(self, state):
        if ( state == "active" ):
            return "gtk-media-play"
        elif ( state == "paused" ):
            return "gtk-media-pause"
        elif ( state == "waiting" ):
            return "gtk-execute"
        elif ( state == "disconnected" ):
            return "gtk-media-stop"
        elif ( state == "finished" ):
            return "gtk-ok"
    
    
    ## HELPER: human readable
    def human_filesize(self, size):
        num = float(size)

        for x in ['bytes','KB','MB','GB','TB']:
            if ( num < 1024.0 ):
                return "%3.1f %s" % (num, x)
            num /= 1024.0         
       
    

## main ##
def main():
    ## read parameters
    SOCK_ADDR = sys.argv[1]
    
    try:
        app = OmnibusDriver(unix_path=SOCK_ADDR)
    except Exception as e:
        print >> sys.stderr, 'Could not start omnibus-driver'
        print >> sys.stderr, 'Error message:', e
        return 1
    
    app.run()
    
    return 0
